//=====================================================================
// Accel[erated] [an]imation object
// change a property of an object over time in an accelerated fashion
//=====================================================================
// obj  : reference to the object whose property you'd like to animate
// prop : property you would like to change eg: "left"
// to   : final value of prop
// time : time the animation should take to run
// zip    : optional. specify the zippiness of the acceleration. pick a 
//          number between -1 and 1 where -1 is full decelerated, 1 is 
//          full accelerated, and 0 is linear (no acceleration). default
//          is 0.
// unit    : optional. specify the units for use with prop. default is 
//          "px".
//=====================================================================

function Accelimation(obj, prop, to, time, zip, unit) {
    if (typeof zip  == "undefined") zip  = 0;
    if (typeof unit == "undefined") unit = "px";

    if (zip > 2 || zip <= 0)
        throw new Error("Illegal value for zip. Must be less than or equal to 2 and greater than 0.");

    this.obj    = obj;
    this.prop    = prop;
    this.x1        = to;
    this.dt        = time;
    this.zip    = zip;
    this.unit    = unit;

    this.x0        = parseInt(this.obj[this.prop]);
    this.D        = this.x1 - this.x0;
    this.A        = this.D / Math.abs(Math.pow(time, this.zip));
    this.id        = Accelimation.instances.length;
    this.onend    = null;
}



//=====================================================================
// public methods
//=====================================================================

// after you create an accelimation, you call this to start it-a runnin'
Accelimation.prototype.start = function() {
    this.t0 = new Date().getTime();
    this.t1 = this.t0 + this.dt;
    var dx    = this.x1 - this.x0;
    Accelimation._add(this);
}

// and if you need to stop it early for some reason...
Accelimation.prototype.stop = function() {
    Accelimation._remove(this);
}



//=====================================================================
// private methods
//=====================================================================

// paints one frame. gets called by Accelimation._paintAll.
Accelimation.prototype._paint = function(time) {
    if (time < this.t1) {
        var elapsed = time - this.t0;
        this.obj[this.prop] = Math.abs(Math.pow(elapsed, this.zip)) * this.A + this.x0 + this.unit;
    }
    else this._end();
}

// ends the animation
Accelimation.prototype._end = function() {
    Accelimation._remove(this);
    this.obj[this.prop] = this.x1 + this.unit;

    if (this.onend) {
      this.onend();
    }
}




//=====================================================================
// static methods (all private)
//=====================================================================

// add a function to the list of ones to call periodically
Accelimation._add = function(o) {
    var index = this.instances.length;
    this.instances[index] = o;
    // if this is the first one, start the engine
    if (this.instances.length == 1) {
        this.timerID = window.setInterval("Accelimation._paintAll()", this.targetRes);
    }
}

// remove a function from the list
Accelimation._remove = function(o) {
    for (var i = 0; i < this.instances.length; i++) {
        if (o == this.instances[i]) {
            this.instances = this.instances.slice(0,i).concat( this.instances.slice(i+1) );
            break;
        }
    }
    // if that was the last one, stop the engine
    if (this.instances.length == 0) {
        window.clearInterval(this.timerID);
        this.timerID = null;
    }
}

// "engine" - call each function in the list every so often
Accelimation._paintAll = function() {
    var now = new Date().getTime();
    for (var i = 0; i < this.instances.length; i++) {
        // small chance that this accelimation could get dropped in the queue in the middle
        // of a run. That means that it could have a t0 greater than "now", which means that
        // elapsed would be negative.
        this.instances[i]._paint(Math.max(now, this.instances[i].t0));
    }
}


//=====================================================================
// static properties
//=====================================================================

Accelimation.instances = [];
Accelimation.targetRes = 10;
Accelimation.timerID = null;
